#pragma once

#include "define.h"
#include "cgnslib.h"

namespace cgns {

const int CGNS_STRING_SIZE = 65;

class CGNSMeshReader;

template<typename T>
void hashCombine(std::size_t& seed, const T& val)
{
	seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

class Edge
{
private:
	cgsize_t n1, n2;
	cgsize_t leftFace, rightFace;

public:
	Edge() : n1(0), n2(0), leftFace(0), rightFace(0) {};

	Edge(const int &p1, const int &p2) :n1(p1), n2(p2), leftFace(0), rightFace(0) {
		if (n1 > n2) std::swap(n1, n2); // let n1 < n2
	};

	Edge(const Edge &other) {
		n1 = other.n1;
		n2 = other.n2;
		leftFace  = other.leftFace;
		rightFace = other.rightFace;
	}

	inline std::size_t operator()(const Edge &other) const
	{
		std::size_t h1 = std::hash<cgsize_t>{}(other.getNode1());
		std::size_t h2 = std::hash<cgsize_t>{}(other.getNode2());

		std::size_t seed = 0;
		hashCombine(seed, other.getNode1());
		hashCombine(seed, other.getNode2());
		return seed;
	}

	cgsize_t getNode1()const { return n1; }
	cgsize_t getNode2()const { return n2; }

	cgsize_t getLeftFace()const { return leftFace; }
	cgsize_t getRightFace()const { return rightFace; }

	void setLeftFace(const int f) { leftFace = f; }
	void setRightFace(const int f) { rightFace = f; }

	bool operator== (const Edge &edge) const {
		return (n1 == edge.n1 && n2 == edge.n2);
	}	

	bool operator < (const Edge& edge) const
	{
		if (n1 != edge.n1) {
			return n1 < edge.n1;
		}
		else {
			if (n2 != edge.n2)
				return n2 < edge.n2;
			else return false;
		}
	}

	int isSame(const int &p1, const int &p2)
	{
		if (n1 == p1 && n2 == p2)
			return 1;
		else if (n1 == p2 && n2 == p1)
			return -1;
		else
			return 0;
	}
};

class Face
{
private:
	std::vector<cgsize_t> nodes;
	int bcRecords;
	int leftCell, rightCell;

public:
	int index;

	Face() :index(0), bcRecords(-1), leftCell(-1), rightCell(-1){};
	Face(const std::vector<cgsize_t> &pnts)
		:index(0), bcRecords(-1), leftCell(-1), rightCell(-1), nodes(pnts) {};

	Face(const Face &other) {
		nodes.assign(other.nodes.begin(), other.nodes.end());
		index     = other.index;
		bcRecords = other.bcRecords;
		leftCell  = other.leftCell;
		rightCell = other.rightCell;
	};

	void setLeftCell(int lc) { leftCell = lc; }
	void setRightCell(int rc) { rightCell = rc; }

	int getLeftCell() const { return leftCell; }
	int getRightCell() const { return rightCell; }

	void setBCRecord(int bcID) { bcRecords = bcID; }
	int getBCRecord() const { return bcRecords; }

	int numNodes()const { return nodes.size(); }

	void reverseNodeOrder() 
	{ 
		std::reverse(nodes.begin(), nodes.end()); 
	}

	const cgsize_t &node(const int i) const { return nodes[i]; }
	const std::vector<cgsize_t> &getNodes() const { return nodes; }

	void init(const std::vector<cgsize_t> &pnts)
	{
		nodes.assign(pnts.begin(), pnts.end());
	}

	bool operator== (const Face &face) const 
	{
		if (nodes.size() != face.numNodes()) {
			return false;
		}
		else {
			for (auto &fn : face.getNodes()) {
				if (std::find(nodes.begin(), nodes.end(), fn) == nodes.end()) {
					return false;
				}
			}
			return true;
		}			
	}	

	std::size_t hashSeed() const {
		auto nodeSort(nodes);
		std::sort(nodeSort.begin(), nodeSort.end());
		std::size_t seed = 0;
		for (const auto &node : nodeSort) hashCombine(seed, node);
		return seed;
	}

	std::size_t hashKey(const std::size_t &size) const {
		return hashSeed()%size;
	}
};
typedef std::shared_ptr<Face> FaceShrPtr;

class FacePtrHash
{
public:
	inline std::size_t operator()(const FaceShrPtr &other) const
	{
		return other->hashSeed();
	}

	bool operator()(const FaceShrPtr &first, const FaceShrPtr &second) const
	{
		return *first == *second;
	}
};

class BC
{
public:
	string_t   name;
	cgsize_t      startID, endID, numBoundFaces;
	BCType_t      BCType;

	std::vector<cgsize_t> elemList;

	std::vector<cgsize_t> boundFacesID;

	// list of all boundary nodes
	std::vector<cgsize_t> nodeList;
	std::vector<cgsize_t> nPoinPerBFace;
	std::vector<cgsize_t> bFaceNodes; // the local node index
	std::vector<Edge> edges; // edge->local node index

	//
	BC() : BCType(BCTypeNull), startID(0), endID(0), numBoundFaces(0) {};
	BC(const BC &other) {
		name    = other.name;
		startID = other.startID;
		endID   = other.endID;
		numBoundFaces = other.numBoundFaces;
		boundFacesID.assign(other.boundFacesID.begin(), other.boundFacesID.end());
		elemList.assign(other.elemList.begin(), other.elemList.end());
	};
};

class Section
{
public:
	string_t   name;
	ElementType_t elemType;
	cgsize_t      startElem, endElem, numElems;
	bool          isInterior;

	std::vector<cgsize_t> elemTypes;
	std::vector<cgsize_t> nNodePerCell;
	std::vector<cgsize_t> elemConnectivity;
	std::vector<cgsize_t> elemStartOffset;

	//
	Section() : elemType(ElementTypeNull), startElem(0), endElem(0),
		numElems(0), isInterior(true) {}
};

class Zone
{
public:
	Zone(CGNSMeshReader *mesh = nullptr) : cgnsmesh(mesh) {};
	~Zone() {};

	//
	CGNSMeshReader *cgnsmesh;

	string_t name;
	int nGrids;
	int numGlobalPoints;
	int numElems;
	int numBoundPoints;
	int numInteriorElems;
	int numFaces;

	rrvector coordinates;

	int numSections;
	std::vector<Section> sections;

	// face-based connectivity
	std::vector<cgsize_t> face2Node;
	std::vector<cgsize_t> nNodePerFace;
	std::vector<cgsize_t> face2Elems;	

	// cell-based connectivity
	std::vector<cgsize_t> nNodePerCell;
	std::vector<cgsize_t> cell2Node;
	std::vector<cgsize_t> elemTypes;

	// boudaries
	std::vector<BC> bcs;

	// 
	void reconstruct();

	//
	int getSectionID(const int elemID);
};

class CGNSMeshReader
{
public:
	CGNSMeshReader(string_t cgFileName, unsigned int verb = 1);
	~CGNSMeshReader() {};

	//
	unsigned int verbose;

	//
	int getErrorCode()const { return errorCode; }

	//
	int getCellDim() { return cellDim; };

	// Get number of zones
	int getNZones() { return nZones; }

	// Get zone by index
	Zone &getZone(int i) { return zones[i]; }
		
	// Open the CGNS file
	void openFile(string_t cgFileName);

	// Read all CGNS database
	void readBaseInfo();
	
	// Read all CGNS zone
	void readZoneInfo();

	// Read all CGNS section
	void readSection();

	// Read all CGNS boundary condition
	void readBoundaryCondition();

	// Write mesh to tecplot ascii file.
	void writeToTecplotASCII(string_t tecFileName);
	void writeToTecplotASCII_CellBased(string_t tecFileName);

private:

	/*  Dimension of the cells :
	 *    3 for volume cells, 
	 *    2 for surface cells, 
	 *    1 for line cells.
	 */
	int cellDim;

	//Number of coordinates required to define a vector in the field.
	int physDim;

	int cgFile;   /* CGNS file identifier. */
	int cgBase;   /* CGNS database index. */
	int cgZone;   /* CGNS zone index. */
	int cgBC;     /* CGNS boundary index. */
	int cgSection;/* CGNS section index. */

	int nBases;     /* Total number of bases in the CGNS file. */
	int nZones;     /* Total number of zones in the CGNS file. */
	int nSections;  /* Total number of sections in the CGNS file. */

	//
	std::vector<Zone> zones;

	//
	int errorCode;
};

};
